;==============================================================================
; BED TEMPERATURE SENSOR CALIBRATION SCRIPT
; This script automatically calibrates the bed temperature sensor by finding
; the optimal C coefficient through a binary search algorithm
;==============================================================================

;========= CONFIGURABLE TEMPERATURE PARAMETERS =========
; Edit these values to customize calibration temperatures
var bedTargetTemp = 160      ; Default target bed temperature for calibration (°C)

; User interaction: Prepare bed for calibration FIRST
M291 S2 R"Prepare Build Plate" P"Please place the carbon fiber sheet or your print surface on top of the build plate for accurate temperature measurement." S2

; ---- User confirmation and temperature selection ----
M291 S4 K{"Start with 160°C","Custom temperature","Cancel"} R"Bed Temperature Calibration" P"The printer will calibrate bed sensor.<br>Hold X or U endstop to abort."

if input == 0
  ; Use default temperature (160°C)
  echo "Starting bed calibration at default temperature: " ^ var.bedTargetTemp ^ "°C"
elif input == 1
  ; Ask for custom temperature
  M291 J1 L70 H200 F{var.bedTargetTemp} P"Enter the target bed heating temperature (°C)" R"Enter the target temperature" S5
  if result == -1
    abort "Calibration cancelled by user."
  set var.bedTargetTemp = input
  echo "Starting bed calibration at custom temperature: " ^ var.bedTargetTemp ^ "°C"
else
  abort "Calibration cancelled by user."

; Reset all heating systems to safe state
M568 P0 S{0} R{0}
M568 P1 S{0} R{0}
M568 P2 S{0} R{0}
M568 P3 S{0} R{0}

; ---- Read configuration version ----
var macrosVersionContent = fileread("0:/sys/version.txt", 0, 2, ',')
var macrosVersion = var.macrosVersionContent[0] ^ ""
var macrosReleaseDate = var.macrosVersionContent[1] ^ ""

; ---- Create Temperature Log File ----
var logFile = "0:/sys/logs/sensor_bed.csv"
var paramFile = "0:/sys/logs/sensor_bed.txt"
var logLine = ""

echo >{var.logFile} "# Sensor Calibration — Bed Temperature Sensor (S2)"
echo >>{var.logFile} "# Target: " ^ var.bedTargetTemp ^ "C"
echo >>{var.logFile} "# Mainboard FW: " ^ boards[0].firmwareFileName ^ " v" ^ boards[0].firmwareVersion
echo >>{var.logFile} "# Expansion FW: " ^ boards[1].firmwareFileName ^ " v" ^ boards[1].firmwareVersion
echo >>{var.logFile} "# Macros ver.: " ^ var.macrosVersion ^ " (" ^ var.macrosReleaseDate ^ ")"
echo >>{var.logFile} "#"

var logHeader = "Elapsed(s),Bed(C),ChamberAir(C),ChamberHeater(C),LeftNozzle(C),RightNozzle(C)"
set var.logHeader = var.logHeader ^ ",BedState,ChamberState,HEPAFan(RPM),BedPWM,ChamberPWM"
echo >>{var.logFile} var.logHeader

var startTime = state.upTime
var elapsed = 0

; Log initial state
set var.logLine = "0," ^ heat.heaters[2].current
set var.logLine = var.logLine ^ "," ^ heat.heaters[3].current
set var.logLine = var.logLine ^ "," ^ sensors.analog[4].lastReading
set var.logLine = var.logLine ^ "," ^ heat.heaters[0].current
set var.logLine = var.logLine ^ "," ^ heat.heaters[1].current
set var.logLine = var.logLine ^ "," ^ heat.heaters[2].state
set var.logLine = var.logLine ^ "," ^ heat.heaters[3].state
set var.logLine = var.logLine ^ "," ^ fans[7].rpm
set var.logLine = var.logLine ^ "," ^ heat.heaters[2].avgPwm
set var.logLine = var.logLine ^ "," ^ heat.heaters[3].avgPwm
echo >>{var.logFile} var.logLine
echo "=== Sensor Calibration Log Started: " ^ var.logFile ^ " ==="

; ---- Save Heater Configuration Parameters ----
var paramLine = ""
echo >{var.paramFile} "=== Bed Sensor Calibration Configuration Snapshot ==="
echo >>{var.paramFile} "Date: " ^ state.time
echo >>{var.paramFile} "Target: " ^ var.bedTargetTemp ^ "C"
echo >>{var.paramFile} ""

; H0 - Left Nozzle
echo >>{var.paramFile} "--- H0 (Left Nozzle) ---"
set var.paramLine = "M307 H0 R" ^ heat.heaters[0].model.heatingRate
set var.paramLine = var.paramLine ^ " K" ^ heat.heaters[0].model.coolingRate ^ ":" ^ heat.heaters[0].model.fanCoolingRate
set var.paramLine = var.paramLine ^ " D" ^ heat.heaters[0].model.deadTime
set var.paramLine = var.paramLine ^ " E" ^ heat.heaters[0].model.coolingExp
set var.paramLine = var.paramLine ^ " S" ^ heat.heaters[0].model.enabled
echo >>{var.paramFile} var.paramLine
if #heat.heaters[0].monitors > 0
  var h0m = 0
  while var.h0m < #heat.heaters[0].monitors
    if heat.heaters[0].monitors[var.h0m].condition != "disabled"
      echo >>{var.paramFile} "  Monitor " ^ var.h0m ^ ": condition=" ^ heat.heaters[0].monitors[var.h0m].condition ^ " limit=" ^ heat.heaters[0].monitors[var.h0m].limit ^ " action=" ^ heat.heaters[0].monitors[var.h0m].action
    set var.h0m = var.h0m + 1
echo >>{var.paramFile} ""

; H1 - Right Nozzle
echo >>{var.paramFile} "--- H1 (Right Nozzle) ---"
set var.paramLine = "M307 H1 R" ^ heat.heaters[1].model.heatingRate
set var.paramLine = var.paramLine ^ " K" ^ heat.heaters[1].model.coolingRate ^ ":" ^ heat.heaters[1].model.fanCoolingRate
set var.paramLine = var.paramLine ^ " D" ^ heat.heaters[1].model.deadTime
set var.paramLine = var.paramLine ^ " E" ^ heat.heaters[1].model.coolingExp
set var.paramLine = var.paramLine ^ " S" ^ heat.heaters[1].model.enabled
echo >>{var.paramFile} var.paramLine
if #heat.heaters[1].monitors > 0
  var h1m = 0
  while var.h1m < #heat.heaters[1].monitors
    if heat.heaters[1].monitors[var.h1m].condition != "disabled"
      echo >>{var.paramFile} "  Monitor " ^ var.h1m ^ ": condition=" ^ heat.heaters[1].monitors[var.h1m].condition ^ " limit=" ^ heat.heaters[1].monitors[var.h1m].limit ^ " action=" ^ heat.heaters[1].monitors[var.h1m].action
    set var.h1m = var.h1m + 1
echo >>{var.paramFile} ""

; H2 - Bed
echo >>{var.paramFile} "--- H2 (Bed Heater) ---"
set var.paramLine = "M307 H2 R" ^ heat.heaters[2].model.heatingRate
set var.paramLine = var.paramLine ^ " K" ^ heat.heaters[2].model.coolingRate ^ ":" ^ heat.heaters[2].model.fanCoolingRate
set var.paramLine = var.paramLine ^ " D" ^ heat.heaters[2].model.deadTime
set var.paramLine = var.paramLine ^ " E" ^ heat.heaters[2].model.coolingExp
set var.paramLine = var.paramLine ^ " S" ^ heat.heaters[2].model.enabled
echo >>{var.paramFile} var.paramLine
if #heat.heaters[2].monitors > 0
  var h2m = 0
  while var.h2m < #heat.heaters[2].monitors
    if heat.heaters[2].monitors[var.h2m].condition != "disabled"
      echo >>{var.paramFile} "  Monitor " ^ var.h2m ^ ": condition=" ^ heat.heaters[2].monitors[var.h2m].condition ^ " limit=" ^ heat.heaters[2].monitors[var.h2m].limit ^ " action=" ^ heat.heaters[2].monitors[var.h2m].action
    set var.h2m = var.h2m + 1
echo >>{var.paramFile} ""

; H3 - Chamber
echo >>{var.paramFile} "--- H3 (Chamber Heater) ---"
set var.paramLine = "M307 H3 R" ^ heat.heaters[3].model.heatingRate
set var.paramLine = var.paramLine ^ " K" ^ heat.heaters[3].model.coolingRate ^ ":" ^ heat.heaters[3].model.fanCoolingRate
set var.paramLine = var.paramLine ^ " D" ^ heat.heaters[3].model.deadTime
set var.paramLine = var.paramLine ^ " E" ^ heat.heaters[3].model.coolingExp
set var.paramLine = var.paramLine ^ " S" ^ heat.heaters[3].model.enabled
echo >>{var.paramFile} var.paramLine
if #heat.heaters[3].monitors > 0
  var h3m = 0
  while var.h3m < #heat.heaters[3].monitors
    if heat.heaters[3].monitors[var.h3m].condition != "disabled"
      echo >>{var.paramFile} "  Monitor " ^ var.h3m ^ ": condition=" ^ heat.heaters[3].monitors[var.h3m].condition ^ " limit=" ^ heat.heaters[3].monitors[var.h3m].limit ^ " action=" ^ heat.heaters[3].monitors[var.h3m].action
    set var.h3m = var.h3m + 1

; Heater PWM and Heating Rates
echo >>{var.paramFile} ""
echo >>{var.paramFile} "=== Heater PWM (at test start) ==="
echo >>{var.paramFile} "H0 (Left Nozzle)  avgPwm: " ^ heat.heaters[0].avgPwm
echo >>{var.paramFile} "H1 (Right Nozzle) avgPwm: " ^ heat.heaters[1].avgPwm
echo >>{var.paramFile} "H2 (Bed Heater)   avgPwm: " ^ heat.heaters[2].avgPwm
echo >>{var.paramFile} "H3 (Chamber)      avgPwm: " ^ heat.heaters[3].avgPwm

echo >>{var.paramFile} ""
echo >>{var.paramFile} "=== Heating Rates (from model) ==="
echo >>{var.paramFile} "H0 heatingRate: " ^ heat.heaters[0].model.heatingRate
echo >>{var.paramFile} "H1 heatingRate: " ^ heat.heaters[1].model.heatingRate
echo >>{var.paramFile} "H2 heatingRate: " ^ heat.heaters[2].model.heatingRate
echo >>{var.paramFile} "H3 heatingRate: " ^ heat.heaters[3].model.heatingRate

echo >>{var.paramFile} ""
echo >>{var.paramFile} "=== Board Voltages (at test start) ==="
echo >>{var.paramFile} "Board 0 Vin: " ^ boards[0].vIn.current ^ "V (min: " ^ boards[0].vIn.min ^ " max: " ^ boards[0].vIn.max ^ ")"
echo >>{var.paramFile} "Board 1 Vin: " ^ boards[1].vIn.current ^ "V (min: " ^ boards[1].vIn.min ^ " max: " ^ boards[1].vIn.max ^ ")"

echo >>{var.paramFile} ""
echo >>{var.paramFile} "=== MCU Temperatures (at test start) ==="
echo >>{var.paramFile} "Board 0 MCU: " ^ boards[0].mcuTemp.current ^ "C (min: " ^ boards[0].mcuTemp.min ^ " max: " ^ boards[0].mcuTemp.max ^ ")"
echo >>{var.paramFile} "Board 1 MCU: " ^ boards[1].mcuTemp.current ^ "C (min: " ^ boards[1].mcuTemp.min ^ " max: " ^ boards[1].mcuTemp.max ^ ")"

echo >>{var.paramFile} ""
echo >>{var.paramFile} "=== Firmware ==="
echo >>{var.paramFile} "Board 0: " ^ boards[0].firmwareFileName ^ " v" ^ boards[0].firmwareVersion
echo >>{var.paramFile} "Board 1: " ^ boards[1].firmwareFileName ^ " v" ^ boards[1].firmwareVersion
echo >>{var.paramFile} "Macros ver.: " ^ var.macrosVersion ^ " (" ^ var.macrosReleaseDate ^ ")"

echo "Heater parameters saved to: " ^ var.paramFile

;========= HEATING PHASE FOR CALIBRATION =========
echo "Starting heating phase - Bed target: " ^ var.bedTargetTemp ^ "°C"
M140 S{var.bedTargetTemp}
M98 P"0:/sys/led/start_cold.g"

; Home all axes while bed is heating
echo "Homing axes while bed is heating..."
G28
M98 P"0:/sys/led/start_cold.g"

; Position tool heads and move build plate down
G90
G1 X-100 Y0 U100 F18000
G1 Z300 F6000
M400

; Wait for bed heater to reach within 5°C of target
echo "Waiting for bed temperature to reach target..."
while abs(heat.heaters[2].current - var.bedTargetTemp) > 5
  G4 S1
  set var.elapsed = state.upTime - var.startTime
  set var.logLine = var.elapsed ^ "," ^ heat.heaters[2].current
  set var.logLine = var.logLine ^ "," ^ heat.heaters[3].current
  set var.logLine = var.logLine ^ "," ^ sensors.analog[4].lastReading
  set var.logLine = var.logLine ^ "," ^ heat.heaters[0].current
  set var.logLine = var.logLine ^ "," ^ heat.heaters[1].current
  set var.logLine = var.logLine ^ "," ^ heat.heaters[2].state
  set var.logLine = var.logLine ^ "," ^ heat.heaters[3].state
  set var.logLine = var.logLine ^ "," ^ fans[7].rpm
  set var.logLine = var.logLine ^ "," ^ heat.heaters[2].avgPwm
  set var.logLine = var.logLine ^ "," ^ heat.heaters[3].avgPwm
  echo >>{var.logFile} var.logLine

; Switch to yellow LEDs when temperature is reached
M98 P"0:/sys/led/pause.g"        ; YELLOW = temperature reached, stabilizing

; Initialize variables for stabilization countdown
var remainingTime = 0
var lastUpdateTime = 0
var stabilizeStart = state.upTime

echo "Temperature reached - starting 10-minute stabilization period"

; 10-minute stabilization period with logging and endstop monitoring
while !(sensors.endstops[0].triggered || sensors.endstops[3].triggered)
  set var.remainingTime = 600 - (state.upTime - var.stabilizeStart)
  
  ; Check if stabilization period is complete
  if var.remainingTime <= 0
    break
  
  ; Provide time update every 30 seconds
  if (state.upTime - var.lastUpdateTime) >= 30 || var.lastUpdateTime == 0
    set var.lastUpdateTime = state.upTime
    echo "Stabilization - " ^ (var.remainingTime / 60.0) ^ " min remaining (Hold X or U endstop to skip)"
  
  ; Log temperature every second
  set var.elapsed = state.upTime - var.startTime
  set var.logLine = var.elapsed ^ "," ^ heat.heaters[2].current
  set var.logLine = var.logLine ^ "," ^ heat.heaters[3].current
  set var.logLine = var.logLine ^ "," ^ sensors.analog[4].lastReading
  set var.logLine = var.logLine ^ "," ^ heat.heaters[0].current
  set var.logLine = var.logLine ^ "," ^ heat.heaters[1].current
  set var.logLine = var.logLine ^ "," ^ heat.heaters[2].state
  set var.logLine = var.logLine ^ "," ^ heat.heaters[3].state
  set var.logLine = var.logLine ^ "," ^ fans[7].rpm
  set var.logLine = var.logLine ^ "," ^ heat.heaters[2].avgPwm
  set var.logLine = var.logLine ^ "," ^ heat.heaters[3].avgPwm
  echo >>{var.logFile} var.logLine
  
  G4 S1  ; Wait 1 second before next check

; Check if stabilization was skipped by endstop
if sensors.endstops[0].triggered || sensors.endstops[3].triggered
  echo "Stabilization period skipped by endstop trigger"
else
  echo "Stabilization complete"

;========= ASK FOR PYROMETER MEASUREMENT =========
M291 J1 L0 H200 F120 P"Please use a pyrometer to measure the temperature of the carbon fiber sheet and enter its value (°C):" R"Enter measured temperature" S5
if result == -1
  M140 S0
  abort "Calibration cancelled by user."
var target = {input}
echo >>{var.logFile} "# Pyrometer reading: " ^ var.target ^ "C"

;========= BINARY SEARCH CALIBRATION ALGORITHM =========
; Use binary search to find optimal C coefficient for temperature sensor
; The C coefficient compensates for sensor variations and improves accuracy

; Initialize calibration parameters
var tol = 0.2        ; Tolerance: stop when within 0.2°C of target
var cLo = -1.5e-7    ; Lower bound of C coefficient search range
var cHi =  1.5e-7    ; Upper bound of C coefficient search range
var bestC = 0.0      ; Best C coefficient found so far
var bestErr = 1.0e9  ; Best error achieved so far (start with large value)
var cMid = 0.0       ; Current middle value being tested

; Main calibration loop - binary search algorithm
var iterations = 0
while true
  ; Check for abort condition (X or U endstop triggered)
  if sensors.endstops[0].triggered || sensors.endstops[3].triggered
    M140 S0
    M98 P"0:/sys/led/fault.g"    ; RED
    echo >>{var.logFile} "RESULT: Calibration aborted by endstop"
    abort "Calibration canceled due to triggered X/U endstop."
  
  ; Calculate middle point of current search range
  set var.cMid = (var.cLo + var.cHi)/2.0
  
  ; Apply the test C coefficient to sensor 2 (bed sensor)
  M308 S2 C{var.cMid}
  G4 S5  ; Wait for sensor reading to stabilize
  
  ; Log temperature
  set var.elapsed = state.upTime - var.startTime
  set var.logLine = var.elapsed ^ "," ^ heat.heaters[2].current
  set var.logLine = var.logLine ^ "," ^ heat.heaters[3].current
  set var.logLine = var.logLine ^ "," ^ sensors.analog[4].lastReading
  set var.logLine = var.logLine ^ "," ^ heat.heaters[0].current
  set var.logLine = var.logLine ^ "," ^ heat.heaters[1].current
  set var.logLine = var.logLine ^ "," ^ heat.heaters[2].state
  set var.logLine = var.logLine ^ "," ^ heat.heaters[3].state
  set var.logLine = var.logLine ^ "," ^ fans[7].rpm
  set var.logLine = var.logLine ^ "," ^ heat.heaters[2].avgPwm
  set var.logLine = var.logLine ^ "," ^ heat.heaters[3].avgPwm
  echo >>{var.logFile} var.logLine
  
  ; Read current temperature from sensor and calculate error against target
  var tNow = sensors.analog[2].lastReading
  var err  = abs(var.tNow - var.target)
  
  ; Progress feedback every 10 iterations
  set var.iterations = var.iterations + 1
  if mod(var.iterations, 10) == 0
    echo "Calibration iteration " ^ var.iterations ^ ": Target=" ^ var.target ^ "°C, Sensor=" ^ var.tNow ^ "°C, Error=" ^ var.err ^ "°C"
  
  ; Update best values if this attempt is better
  if (var.err < var.bestErr)
    set var.bestErr = var.err
    set var.bestC   = var.cMid
    echo "Attempting C coefficient: " ^ var.cMid ^ " with error " ^ var.bestErr ^ "°C"
  
  ; Check if we've found a good enough solution or search range is too small
  if (var.err <= var.tol) || (abs(var.cHi - var.cLo) < 5e-10)
    ; Format the final C coefficient for display and file output
    var scale = 100
    var cUnits = abs(var.bestC) / 1e-8
    var eps = 1e-9 * var.scale
    var cMant = ceil(var.cUnits * var.scale - 0.5 + var.eps) / var.scale
    var cSign = (var.bestC < 0) ? "-" : ""
    
    ; Display calibration results
    echo "=== CALIBRATION COMPLETE ==="
    echo "C coefficient = " ^ var.cSign ^ var.cMant ^ "e-8"
    echo "Target Temperature (pyrometer): " ^ var.target ^ "°C"
    echo "Bed Sensor Reading: " ^ sensors.analog[2].lastReading ^ "°C"
    echo "Final Error: " ^ var.bestErr ^ "°C"
    echo "Total iterations: " ^ var.iterations
    
    ; Save calibrated sensor configuration to file
    echo >"0:/sys/user/actions/BedTempCalibration.g" "M308 S2 A""Bed Heater"" P""1.temp1"" Y""thermistor"" T100000 B3950 C" ^ var.cSign ^ var.cMant ^ "e-8 R2200"
    echo >>{var.logFile} "RESULT: Calibration complete - C=" ^ var.cSign ^ var.cMant ^ "e-8 error=" ^ var.bestErr ^ "C"
    break  ; Exit calibration loop
  
  ; Adjust search range based on temperature reading vs target
  if (var.tNow > var.target)
    set var.cHi = var.cMid  ; Reading too high, search lower C values    
  else
    set var.cLo = var.cMid  ; Reading too low, search higher C values
  
  ; Safety timeout to prevent infinite loops (max 200 iterations)
  if var.iterations > 200
    echo "WARNING: Maximum iterations reached. Using best coefficient found."
    echo >>{var.logFile} "RESULT: Max iterations - C=" ^ var.bestC ^ " error=" ^ var.bestErr ^ "C"
    break     

;========= CALIBRATION COMPLETION AND CLEANUP =========
G4 S5
M105

; Log final entry
var totalTime = state.upTime - var.startTime
set var.logLine = var.totalTime ^ "," ^ heat.heaters[2].current
set var.logLine = var.logLine ^ "," ^ heat.heaters[3].current
set var.logLine = var.logLine ^ "," ^ sensors.analog[4].lastReading
set var.logLine = var.logLine ^ "," ^ heat.heaters[0].current
set var.logLine = var.logLine ^ "," ^ heat.heaters[1].current
set var.logLine = var.logLine ^ "," ^ heat.heaters[2].state
set var.logLine = var.logLine ^ "," ^ heat.heaters[3].state
set var.logLine = var.logLine ^ "," ^ fans[7].rpm
set var.logLine = var.logLine ^ "," ^ heat.heaters[2].avgPwm
set var.logLine = var.logLine ^ "," ^ heat.heaters[3].avgPwm
echo >>{var.logFile} var.logLine
echo >>{var.logFile} "RESULT: Sensor calibration completed in " ^ var.totalTime ^ "s"
echo "=== Sensor Calibration Log Complete: " ^ var.totalTime ^ "s ==="

; Cool down bed heater
M140 S0

; Visual feedback and completion notifications
M98 P"0:/sys/led/end.g"
M291 P"Calibration completed successfully!" R"Success" S2
M98 P"0:/sys/led/resetstatus.g"

;==============================================================================
; CALIBRATION COMPLETE
; The bed temperature sensor is now calibrated with the optimal C coefficient
; Configuration saved to: /sys/user/actions/BedTempCalibration.g
;==============================================================================